package Game2048;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Arrays;
import java.util.Observable;
import java.util.Random;

import org.eclipse.swt.SWT;

import controller.Presenter;
import controller.XMLHandler;
import model.Model;
import model.State;

/**
 * The 2048 Game Model. Responsible for all the game logic.<br>
 * extends {@link Observable}, implements {@link Model}
 * @author Barak & Tom
 */
public class Game2048Model extends Observable implements Model {
	private int[][] modelBoard;
	private int currentScore;
	public State currentState;
	public State tmpState;
	private int stepId = 0;
	Random generator = new Random();
	private int[] initOptions = new int[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 4 };
	 private int win;

	public Game2048Model() {
		modelBoard = new int[][] { { 0, 0, 0, 0 }, { 0, 0, 0, 0 },
				{ 0, 0, 0, 0 }, { 0, 0, 0, 0 } };
		currentScore = 0;
		
//		currentState = new State(-1, currentScore, cloneBoard(modelBoard), null);
	}

	@Override
	public State moveUp(State currentState) {
		int[][] modelBoard = cloneBoard(currentState.boardState);
		int currentScore = currentState.score;
		int stepId = currentState.stateId;
		boolean newstate = false;
		for (int i = 0; i < modelBoard.length; i++) {
			if(!isChanged("up", i)){ continue; }
			for (int j = 1; j < modelBoard[i].length; j++) {
				if (modelBoard[i][j] != 0) {
					if (modelBoard[i][j - 1] == modelBoard[i][j]) {
						modelBoard[i][j - 1] *= 2;
						currentScore += modelBoard[i][j - 1];
						modelBoard[i][j] = 0;
						newstate = true;
					} else if (modelBoard[i][j - 1] == 0) {
						int k = 0;
						while (j - k > 0) {
							if (modelBoard[i][j - k - 1] == modelBoard[i][j - k]) {
								modelBoard[i][j - k - 1] *= 2;
								currentScore += modelBoard[i][j - k - 1]; 
								modelBoard[i][j - k] = 0;
								newstate = true;
								break;
							} else if (modelBoard[i][j - k - 1] == 0) {
								modelBoard[i][j - k - 1] = modelBoard[i][j - k];
								modelBoard[i][j - k] = 0;
								k++;
								newstate = true;
							} else {
								break;
							}
						}
					}
				}
			}
		}
		if(newstate){
			return new State(stepId++, currentScore, modelBoard, currentState);
		}
		return null;
	}

	@Override
	public State moveDown(State currentState) {
		int[][] modelBoard = cloneBoard(currentState.boardState);
		int currentScore = currentState.score;
		int stepId = currentState.stateId;
		boolean newstate = false;
		for (int i = 0; i < modelBoard.length; i++) {
			if(!isChanged("down", i)){ continue; }
			for (int j = modelBoard[i].length - 2; j >= 0; j--) {
				if (modelBoard[i][j] != 0) {
					if (modelBoard[i][j + 1] == modelBoard[i][j]) {
						modelBoard[i][j + 1] *= 2;
						currentScore += modelBoard[i][j + 1];
						modelBoard[i][j] = 0;
						newstate = true;
					} else if (modelBoard[i][j + 1] == 0) {
						int k = 0;
						while (j + k < 3) {
							if (modelBoard[i][j + k + 1] == modelBoard[i][j + k]) {
								modelBoard[i][j + k + 1] *= 2;
								currentScore +=modelBoard[i][j + k + 1];
								modelBoard[i][j + k] = 0;
								newstate = true;
								break;
							} else if (modelBoard[i][j + k + 1] == 0) {
								modelBoard[i][j + k + 1] = modelBoard[i][j + k];
								modelBoard[i][j + k] = 0;
								k++;
								newstate = true;
							} else {
								break;
							}
						}
					}
				}
			}
		}
		if(newstate){
		return new State(stepId++, currentScore, modelBoard, currentState);
		}
		return null;
		
	}

	@Override
	public State moveLeft(State currentState) {
		int[][] modelBoard = cloneBoard(currentState.boardState);
		int currentScore = currentState.score;
		int stepId = currentState.stateId;
		boolean newstate = false;
		for (int i = 1; i < modelBoard.length; i++) {
			for (int j = 0; j < modelBoard[i].length; j++) {
				if(!isChanged("left", j)){ continue; }
				if (modelBoard[i][j] != 0) {
					if (modelBoard[i - 1][j] == modelBoard[i][j]) {
						modelBoard[i - 1][j] *= 2;
						currentScore += modelBoard[i - 1][j];
						modelBoard[i][j] = 0;
						newstate = true;
					} else if (modelBoard[i - 1][j] == 0) {
						int k = 0;
						while (i - k > 0) {
							if (modelBoard[i - k - 1][j] == modelBoard[i - k][j]) {
								modelBoard[i - k - 1][j] *= 2;
								currentScore += modelBoard[i - k - 1][j];
								modelBoard[i - k][j] = 0;
								newstate = true;
								break;
							} else if (modelBoard[i - k - 1][j] == 0) {
								modelBoard[i - k - 1][j] = modelBoard[i - k][j];
								modelBoard[i - k][j] = 0;
								k++;
								newstate = true;
							} else {
								break;
							}
						}
					}
				}
			}
		}
		
		if(newstate){
			return new State(stepId++, currentScore, modelBoard, currentState);
		}
		return null;
	}

	@Override
	public State moveRight(State currentState) {
		int[][] modelBoard = cloneBoard(currentState.boardState);
		int currentScore = currentState.score;
		int stepId = currentState.stateId;
		boolean newstate = false;
		for (int i = modelBoard.length - 2; i >= 0; i--) {
			for (int j = 0; j < modelBoard[i].length; j++) {
				if(!isChanged("right", j)){ continue; }
				if (modelBoard[i][j] != 0) {
					if (modelBoard[i + 1][j] == modelBoard[i][j]) {
						modelBoard[i + 1][j] *= 2;
						currentScore += modelBoard[i + 1][j];
						modelBoard[i][j] = 0;
						newstate = true;
					} else if (modelBoard[i + 1][j] == 0) {
						int k = 0;
						while (i + k < 3) {
							if (modelBoard[i + k + 1][j] == modelBoard[i + k][j]) {
								modelBoard[i + k + 1][j] *= 2;
								currentScore += modelBoard[i + k + 1][j];
								modelBoard[i + k][j] = 0;
								newstate = true;
								break;
							} else if (modelBoard[i + k + 1][j] == 0) {
								modelBoard[i + k + 1][j] = modelBoard[i + k][j];
								modelBoard[i + k][j] = 0;
								k++;
								newstate = true;
							} else {
								break;
							}
						}
					}
				}
			}
		}
		
		if(newstate){
			return new State(stepId++, currentScore, modelBoard, currentState);
		}
		return null;
	}
	

	@Override
	public int[][] getData() {
		return this.modelBoard;
	}

	private void notifyToObservers() {
		setChanged();
		notifyObservers();
	}

	@Override
	public void addObserver(Presenter p) {
		super.addObserver(p);
	}

	/**
	 * Inherited from {@link Observable}.<br>
	 * updates the {@link Model} according to the userCommand.
	 * @param userCommand for user command
	 * @param filePath File location in case of Save/Load command
	 */
	@Override
	public void updateMe(int userCommand,String filePath) {
		switch (userCommand) {
		case -1:
			modelClearBoard();
			initOptionsGenerator(modelBoard);
			initOptionsGenerator(modelBoard);
			currentState = new State(-1, currentScore, cloneBoard(modelBoard), null);
			break;
		case -2: //Save
			SaveState(currentState, filePath);
			break;
		case -3: //Load
			State loaded = LoadState(filePath);
			currentState = loaded;
			modelBoard = cloneBoard(loaded.boardState);
			currentScore = loaded.score;
			stepId = loaded.stateId;
			break;
		case -4:
			undo();
			break;
		case SWT.ARROW_UP:
			tmpState = moveUp(currentState);
			break;
		case SWT.ARROW_DOWN:
			tmpState =moveDown(currentState);
			break;
		case SWT.ARROW_LEFT:
			tmpState =moveLeft(currentState);
			break;
		case SWT.ARROW_RIGHT:
			tmpState =moveRight(currentState);
			break;
		default:
			updateMe(getNextState(userCommand),filePath);
			break;
		}
		if (tmpState != null) {
			currentState = tmpState;
			initOptionsGenerator(currentState.boardState);
			tmpState = null;
			modelBoard = currentState.boardState;
			currentScore = currentState.score;
			stepId = currentState.stateId;
			
		}
		notifyToObservers();
	}

	/**
	 * Calculating the next step using Artificial Intelligence
	 * @see State
	 * @return State
	 */
	private int getNextState(int dept) {
		String newState = null;
		try
		{
		System.out.println("Client is up.");
		Socket serverToConnect = new Socket(InetAddress.getLocalHost(), 5000);
		System.out.println("Client connected to Server "+ InetAddress.getLocalHost() + " port 5000");
		BufferedReader dataFromServer = new BufferedReader(new InputStreamReader(serverToConnect.getInputStream()));
		PrintWriter dataToServer = new PrintWriter(new OutputStreamWriter(serverToConnect.getOutputStream()));

		dataToServer.println(currentState.score +"|"+ Arrays.deepToString(currentState.boardState)+"|"+Integer.toString(dept));
		dataToServer.flush();
		System.out.println("My current state sent to the server");
		
		while ((newState = dataFromServer.readLine()) == null) {
			//Busy Wait
			System.out.println("Waiting for response from server...");
			Thread.sleep(2000);
		}
		System.out.println(newState);
		System.out.println("Next move received");
		dataToServer.println("done");
		dataToServer.flush();

		dataToServer.close();
		serverToConnect.close();
		} catch( Exception e)
		{
			e.printStackTrace();
		}
		return Integer.parseInt(newState) ;
	}
	
	private void initOptionsGenerator(int[][] modelBoard) {
		int op = initOptions[generator.nextInt(10)];
		int row, col;
		do {
			row = generator.nextInt(4);
			col = generator.nextInt(4);
		} while ((modelBoard[row][col]) != 0);
		modelBoard[row][col] = op;
	}

	private void modelClearBoard() {
		for (int i = 0; i < modelBoard.length; i++) {
			for (int j = 0; j < modelBoard[i].length; j++) {
				modelBoard[i][j] = 0;
			}
		}
		stepId = 0;
		currentScore = 0;
	}
	
	private boolean isChanged(String move, int index)
	{
		switch (move) {
		case "up": case "down":
			return getHashI(index);										
		case "right": case "left":
			return getHashJ(index);
		default:
			return true;
		}
	}
	
	private boolean getHashI(int index)
	{
		int count = 0;
		for (int x = index; x < index + 1; x++) {
			for (int y = 0; y < modelBoard[x].length; y++) {
				//count += modelBoard[x][y];
				count += currentState.boardState[x][y];
			}
		}
		return count != 0;
	}
	
	private boolean getHashJ(int index)
	{
		int count = 0;
		for (int x = 0; x < modelBoard.length; x++) {
			for (int y = index; y < index + 1; y++) {
				//count += modelBoard[x][y];
				count += currentState.boardState[x][y];
			}
		}
		return count != 0;
	}
	public int[][] cloneBoard(int[][] modelBoard) {
		int[][] newBoard = new int[4][4];
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
				newBoard[i][j] = modelBoard[i][j];
			}
		}
		return newBoard;
	}

	@Override
	public int getScore() {
		return this.currentScore;
	}
	
	public void undo(){
		if (currentState.pState != null){
			modelBoard = cloneBoard(currentState.pState.boardState);
			currentScore = currentState.pState.score;
			stepId -= 1;
			currentState = currentState.pState;
		}
	}
	
	private State LoadState(String filePath) {
		State st = null;
		try {
			st = XMLHandler.read(filePath);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return st;
	}

	private void SaveState(State stateToSave, String filePath) {
		try {
			XMLHandler.write(stateToSave, filePath);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
